package com.ssm.security.authorization;

import com.ssm.security.JWT.JSONResult;
import com.ssm.security.JWTUserDetails;
import com.ssm.security.JWTUtils;
import com.ssm.security.UserStatus2Redis;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

import javax.servlet.*;
import java.io.IOException;

/**负责捕获授权请求的Filter
 * 授权处理程序
 */
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
    private Boolean refreshToken = true;
    private static final String HEADER_REDIS_ID ="r_id";

    private JWTUtils jwtUtils;

    private UserStatus2Redis userStatus2Redis;

    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    public UserStatus2Redis getUserStatus2Redis() {
        return userStatus2Redis;
    }

    public void setUserStatus2Redis(UserStatus2Redis userStatus2Redis) {
        this.userStatus2Redis = userStatus2Redis;
    }

    public JWTUtils getJwtUtils() {
        return jwtUtils;
    }

    public void setJwtUtils(JWTUtils jwtUtils) {
        this.jwtUtils = jwtUtils;
    }

    /**
     * Method that is actually called by the filter chain. Simply delegates to
     * the {@link #invoke(org.springframework.security.web.FilterInvocation)} method.
     *
     * @param request
     *             the servlet request
     * @param response
     *             the servlet response
     * @param chain
     *             the filter chain
     *
     * @throws java.io.IOException
     *              if the filter chain fails
     * @throws javax.servlet.ServletException
     *              if the filter chain fails
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);//封装请求
        invoke(fi);
    }

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public Class<? extends Object> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    /**
     * 判断是否是logout请求，如果是，则关闭生成新token功能，也不再向response输出信息
     * 授权前生成新的token:放在授权前是因为如果用户是在登录状态下，访问的资源没有权限将不会返回该程序，也就不会产生新token
     *                    即使有权限放在授权后也不行，因为如果在访问接口时候使用了response，并且关闭流，那么这里就无法写入token到response的头了
     * 生成新token前需要做几个判断：
     * 1.if (generateToken) 是否允许生成新的token，只是为了功能插拔方便。另外可以在security.xml中对该boolean进行配置，方便功能的热插拔
     * 2.获取请求头的用户名username不为空，用于下面的token与用户是否匹配的判断，如果无法匹配，视作token有问题，将不再成新token
     * 3.authentication不能为anonymous，如果为anonymous就有可能是在com.ssm.security.my.MySecurityContextPersistenceFilter中解析token就出现错误或者是用户是匿名访问
     *
     * 从authentication中获取用户信息较为方便，这样就不用再到数据库中取用户信息，节约时间
     * 判断请求头的用户名与token中的是否一样，是则生成新的token并写入redis和response
     *====================================================================
     * super.beforeInvocation:匹配当前访问的url是否在security中，如果有，则使用uRL的权限进行投票是否有权限访问，
     *                        URL存在但是在数据库中如果没有匹配到角色返回空，若是权限不足返回空，
     * 后面两个不知道是做什么的，但是接口的访问在这两个方法中间进行执行
     *
     * @param fi
     * @throws IOException
     * @throws ServletException
     */
    public void invoke(FilterInvocation fi) throws IOException, ServletException {//大致知道是判断是否经过授权，如果经过这一步的授权，则不用重新授权
        if (refreshToken) {
            Authentication authentication  = SecurityContextHolder.getContext().getAuthentication();
            String username = fi.getRequest().getHeader("username");
            if (authentication !=null && username !=null && !authentication.getPrincipal().equals("anonymousUser")) {
                JWTUserDetails jwtUserDetails = null;
                try {
                    jwtUserDetails = (JWTUserDetails) authentication.getPrincipal();
                } catch (Exception e) {
                   throw new IOException(e.getMessage());
                }
                if (username.equals(jwtUserDetails.getUsername())) {
                    logger.info("user "+ "'"+username+"'"+"generate refresh begin");
                    String JWT = jwtUtils.generateAccessToken(jwtUserDetails, fi.getRequest().getRemoteAddr());
                    if (JWT != null){
                        logger.info("user "+ "'"+username+"'"+"generate refresh token success");
                    }
                    String r_id=fi.getRequest().getHeader(HEADER_REDIS_ID);
                    try {
                        userStatus2Redis.refreshUserStatus(r_id,JWT);
                        logger.info("user "+ "'"+username+"'"+"refresh token 2 redis success");
                    } catch (Exception e) {
                        logger.error("user "+ "'"+username+"'"+"refresh token 2 redis failed cause: " + e.getMessage());
                        throw new IOException(e.getMessage());
                    }
                    fi.getHttpResponse().setHeader("Authentication", JSONResult.fillResultString(200, "refresh_token", JWT, jwtUserDetails.getUsername(),r_id));
                }
            }
        }
        InterceptorStatusToken token = super.beforeInvocation(fi);//所有的授权逻辑都在这里进行，授权不通过会返回null，在返回null前没有进行decide，
        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null); //应该是进行了将token加载进Authentication对象的工作
        }
    }

    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource newSource) {
        this.securityMetadataSource = newSource;
    }

    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }


}
